from dataclasses import dataclass
import numpy as np

from emi.util import NumUtils


@dataclass
class BoxPlot:
    """
    盒图（箱型图）
    """
    min_value: float  ##最小异常值
    min_limit: float  # 最小下限值： 5%
    q1: float  # 4分之一值：25%
    median: float  # 中位数值：50%
    q3: float  # 4分之三值： 75
    max_limit: float  # 最大上限值：95%
    max_value: float  # 最大异常值

    size: int  # 总数
    mean: float  # 平均值

    _name_:str = None

    _extra:str = None

    @staticmethod
    def of_values(values: [], name=None,extra_name = None):
        size = len(values)
        if size == 0:
            bp = BoxPlot(min_value=np.nan,
                       min_limit=np.nan,
                       q1=np.nan,
                       median=np.nan,
                       q3=np.nan,
                       max_limit=np.nan,
                       max_value=np.nan,
                       size=0,
                       mean=0,
                       )
            bp._name_ = name
            bp._extra = extra_name
            return bp
        if size == 1:
            assert not values[0] is None
            bp = BoxPlot(min_value=values[0],
                         min_limit=values[0],
                         q1=values[0],
                         median=values[0],
                         q3=values[0],
                         max_limit=values[0],
                         max_value=values[0],
                         size=1,
                         mean=values[0],
                         )
            bp._name_ = name
            bp._extra = extra_name
            return bp
        # def __the_cmp(a1, a2):
        #     return a1.left - a2.left
        dist_items = sorted(values, reverse=False)

        min_limit = None  # 最小下限值： 5%
        q1 = None  # 4分之一值：25%
        median = None  # 中位数值：50%
        q3 = None  # 4分之三值： 75
        max_limit = None  # 最大上限值：95%

        def __cal_quantiles(pre_probal, cur_probal, target_probal, pre_item, cur_item):
            if NumUtils.is_equal(cur_probal,target_probal,0.0001) or NumUtils.is_equal(pre_item,cur_item,0.001):
                return cur_item
            k = (target_probal - pre_probal) / (cur_probal - target_probal)
            v =  (cur_item * k + pre_item) / (1+k)
            assert v >= pre_item and v<= cur_item
            return v


        pre_probal = 0.0
        pre_item = dist_items[0]
        BASE = size + 1

        for i in range(1,size):
            item =  dist_items[i]
            cur_probal =  (i+1) / BASE
            if min_limit is None and cur_probal >= 0.05:
                min_limit = __cal_quantiles(pre_probal,cur_probal,0.05,pre_item,item)
            if q1 is None and cur_probal >= 0.25:
                q1 = __cal_quantiles(pre_probal,cur_probal,0.25,pre_item,item)
            if median is None and cur_probal >= 0.5:
                median = __cal_quantiles(pre_probal,cur_probal,0.5,pre_item,item)
            if q3 is None and cur_probal >= 0.75:
                q3 = __cal_quantiles(pre_probal,cur_probal,0.75,pre_item,item)
            if max_limit is None and cur_probal >= 0.95:
                max_limit = __cal_quantiles(pre_probal,cur_probal,0.95,pre_item,item)

            pre_probal = cur_probal
            pre_item = item
        if max_limit is None:
            max_limit = dist_items[-1]
        if q3 is None:
            q3 = dist_items[-1]
        sum_value = 0.0
        sum_cnt = 0
        for i in range(0,size):
            v =  dist_items[i]
            if min_limit <= v and max_limit >= v:
                sum_value += v
                sum_cnt+=1
        if sum_cnt > 0:
            mean_value = sum_value / sum_cnt
        else:
            mean_value = (min_limit + max_limit) / 2

        boxPlot =  BoxPlot(min_value=dist_items[0],
                       min_limit=min_limit,
                       q1=q1,
                       median=median,
                       q3=q3,
                       max_limit=max_limit ,
                       max_value=dist_items[-1],
                       size=size,
                       mean=mean_value  ## 5% - 95%之前的平均数
                       )
        boxPlot._name_ = name
        boxPlot._extra = extra_name
        return boxPlot

    def setExtra(self,extra:str):
        self._extra = extra

    @staticmethod
    def printAll(box_plots,extra_name = None):
        if isinstance(box_plots, BoxPlot):
            box_plots = [box_plots]
        header = f"%-25s|%-10s|%-10s|%-10s|%-10s|%-10s|%-10s|%-10s|%-10s|%-10s" % (
        "name", "min", "5%", "25%", "50%", "75%", "95%", "max", "size", "mean")
        extra_colume_size = 0
        if not extra_name is None:
            header = f"{header}|{extra_name}"
            extra_colume_size = 1

        print(header)
        BoxPlot.__print_line(extra_colume_size)

        for box in box_plots:
            if box is None:
                BoxPlot.__print_line(extra_colume_size)
            else:

                row_line =f"%-25s|%-10.2f|%-10.2f|%-10.2f|%-10.2f|%-10.2f|%-10.2f|%-10.2f|%-10d|%-10.2f" % (
                    box._name_, box.min_value,
                    box.min_limit,
                    box.q1,
                    box.median,
                    box.q3,
                    box.max_limit,
                    box.max_value,
                    box.size,
                    box.mean)
                if extra_colume_size > 0:
                    extra_content = "" if box._extra is None else box._extra
                    print(f"{row_line}|{extra_content}")
                else:
                    print(row_line)

    @staticmethod
    def __print_line(extra_columeSize = 0,print_ = True):
        line = "|----------" * (9 + extra_columeSize)
        line = f"-------------------------{line}"
        if print_:
            print(line)
        return line

    @staticmethod
    def get_str(box_plots,extra_name = None):
        if isinstance(box_plots, BoxPlot):
            box_plots = [box_plots]
        header = f"%-25s|%-10s|%-10s|%-10s|%-10s|%-10s|%-10s|%-10s|%-10s|%-10s" % (
        "name", "min", "5%", "25%", "50%", "75%", "95%", "max", "size", "mean")
        extra_colume_size = 0
        if not extra_name is None:
            header = f"{header}|{extra_name}"
            extra_colume_size = 1

        text_str = header
        text_str += "\n"+BoxPlot.__print_line(extra_colume_size, print_=False)
        for box in box_plots:
            if box is None:
                text_str += BoxPlot.__print_line(extra_colume_size,print_=False)
            else:
                row_line =f"%-25s|%-10.2f|%-10.2f|%-10.2f|%-10.2f|%-10.2f|%-10.2f|%-10.2f|%-10d|%-10.2f" % (
                    box._name_, box.min_value,
                    box.min_limit,
                    box.q1,
                    box.median,
                    box.q3,
                    box.max_limit,
                    box.max_value,
                    box.size,
                    box.mean)
                if extra_colume_size > 0:
                    extra_content = "" if box._extra is None else box._extra
                    text_str +=f"\n{row_line}|{extra_content}"
                else:
                    text_str += "\n"+ row_line
        return text_str



@dataclass
class BoxPlot20:
    """
    盒图（箱型图）
    """
    min: float  ##最小异常值
    max: float  # 最大异常值
    q_5:float  ## 5%分为数值
    q_10:float
    q_15:float
    q_20:float
    q_25:float
    q_30:float
    q_35:float
    q_40:float
    q_45:float
    q_50:float
    q_55:float
    q_50:float
    q_60:float
    q_65:float
    q_70:float
    q_75:float
    q_80:float
    q_85:float
    q_90:float
    q_95:float

    @property
    def median(self):
        return self.q_50

    @staticmethod
    def of_values(values: []):
        from emi.util.statistics._Quantiles import Quantiles
        qtile = Quantiles.create(values)
        return BoxPlot20(
                    min = qtile._values[0],
                    max = qtile._values[-1],
                    q_5= qtile.get_value(0.05),
            q_10=qtile.get_value(0.10),
            q_15=qtile.get_value(0.15),
            q_20=qtile.get_value(0.20),
            q_25=qtile.get_value(0.25),
            q_30=qtile.get_value(0.30),
            q_35=qtile.get_value(0.35),
            q_40=qtile.get_value(0.40),
            q_45=qtile.get_value(0.45),
            q_50=qtile.get_value(0.50),
            q_55=qtile.get_value(0.55),
            q_60=qtile.get_value(0.60),
            q_65=qtile.get_value(0.65),
            q_70=qtile.get_value(0.70),
            q_75=qtile.get_value(0.75),
            q_80=qtile.get_value(0.80),
            q_85=qtile.get_value(0.85),
            q_90=qtile.get_value(0.90),
            q_95=qtile.get_value(0.95),
        )


    @staticmethod
    def parse_float_list(float_list:[]):
        assert len(float_list) == 21
        return BoxPlot20(
                    min = float_list[0],
                    max = float_list[1],
                    q_5= float_list[2],
            q_10=float_list[3],
            q_15=float_list[4],
            q_20=float_list[5],
            q_25=float_list[6],
            q_30=float_list[7],
            q_35=float_list[8],
            q_40=float_list[9],
            q_45=float_list[10],
            q_50=float_list[11],
            q_55=float_list[12],
            q_60=float_list[13],
            q_65=float_list[14],
            q_70=float_list[15],
            q_75=float_list[16],
            q_80=float_list[17],
            q_85=float_list[18],
            q_90=float_list[19],
            q_95=float_list[20],
        )

    def to_float_list(self):
        return [self.min,
                self.max,
                self.q_5,
                self.q_10,
                self.q_15,
                self.q_20,
                self.q_25,
                self.q_30,
                self.q_35,
                self.q_40,
                self.q_45,
                self.q_50,
                self.q_55,
                self.q_60,
                self.q_65,
                self.q_70,
                self.q_75,
                self.q_80,
                self.q_85,
                self.q_90,
                self.q_95,
                ]
